/**
 * @author Federico Consoli
 *
 * Released under GNU GPLv3 (http://www.gnu.org/licenses/gpl-3.0.txt)
 */

#include "connect_utility.h"

using namespace std;

int Connection::send_ok(int sd) {
    send(sd, "ok", 2, 0);
    return 0;
}

int Connection::recv_ok(int sd) {
    char ok[2];
    recv(sd, ok, 2, 0);
    if (ok[0] == 'o' && ok[1] == 'k')
        return 0;
    else return 1;
}

int Connection::send_s(string stringa, int sd) {
    char ok[3];
    send(sd, stringa.c_str(), stringa.size() + 1, 0);
    recv(sd, ok, 3, 0);
    stringa.assign(ok);
    if (stringa == "ok") return 0;
    else return -1;
}

int Connection::recv_s(string &stringa, int sd) {
    string ok = "ok";
    char buffer[BUFFERSIZE];
    memset(&buffer, 0, sizeof (buffer));
    recv(sd, buffer, BUFFERSIZE, 0);
    send(sd, ok.c_str(), ok.size() + 1, 0);
    stringa.assign(buffer);
    if (stringa.size()) return 0;
    else return -1;
}

template <typename T>
int Connection::invia(T numero, int sd) {
    string numero_stringa;
    ostringstream out;
    out << numero;
    numero_stringa = out.str();
    send_s(numero_stringa, sd);
    return 0;
}

template <typename K>
int Connection::ricevi(K &numero, int sd) {
    string numero_stringa;
    recv_s(numero_stringa, sd);
    istringstream buffer(numero_stringa);
    buffer >> numero;
    return 0;
}

int Connection::send_int(int numero, int sd) {
    return invia<int>(numero, sd);
}

int Connection::send_short(unsigned short numero, int sd) {
    return invia<unsigned short>(numero, sd);
}

int Connection::recv_short(unsigned short &numero, int sd) {
    return ricevi<unsigned short>(numero, sd);
}

int Connection::send_size_t(size_t numero, int sd) {
    return invia<size_t > (numero, sd);
}

int Connection::recv_size_t(size_t &numero, int sd) {
    return ricevi<size_t > (numero, sd);
}

int Connection::recv_int(int &numero, int sd) {
    return ricevi<int>(numero, sd);
}

int Connection::send_double(double numero, int sd) {
    return invia<double>(numero, sd);
}

int Connection::recv_double(double &numero, int sd) {
    return ricevi<double>(numero, sd);
}

int Connection::send_buff(char *long_string, int buf_len, int sd) {
    send_int(buf_len, sd);
    int count = 0;
    if (buf_len <= BUFF_TO_SEND) {
        send(sd, long_string, buf_len, 0);
        if (recv_ok(sd)) cout << "ERROR at send buff!\n";
        return 0;
    }
    while (1) {
        send(sd, long_string + count, BUFF_TO_SEND, 0);
        count = count + BUFF_TO_SEND;
        if ((count + BUFF_TO_SEND) >= buf_len) {
            send(sd, long_string + count, buf_len - count, 0);
            break;
        }
        if (recv_ok(sd)) cout << "ERROR at send buff!\n"; //Simple hand-shake
    }
    return 0;
}

int Connection::recv_buff(char *&long_string, int &len, int sd) {
    recv_int(len, sd);
    long_string = new char [len];
    if (len <= BUFF_TO_SEND) {
        recv(sd, long_string, len, 0);
        send_ok(sd);
        return 0;
    }
    int count = 0;
    while (1) {
        recv(sd, long_string + count, BUFF_TO_SEND, 0);
        count = count + BUFF_TO_SEND;
        if ((count + BUFF_TO_SEND) >= len) {
            recv(sd, long_string + count, len - count, 0);
            break;
        }
        send_ok(sd); //Simple hand-shake
    }
    return 0;
}

int Connection::connect_to(string ip, short porta) {
    int sock;
    int con_ret;
    struct sockaddr_in sock_addr; // inizializzo la socket
    sock = socket(PF_INET, SOCK_STREAM, 0); // creo la socket indicandone le caratteristiche
    if (sock == -1) return -1;
    sock_addr.sin_family = PF_INET; // indico il protocollo utilizzato (TCP/IP)
    sock_addr.sin_port = htons(porta); //indico la porta a cui connettere la socket
    sock_addr.sin_addr.s_addr = inet_addr(ip.c_str());
    con_ret = connect(sock, (struct sockaddr *) &sock_addr, sizeof (struct sockaddr)); // mi connetto all'indirizzo scelto
    if (con_ret == -1)
        return -1;
    return sock;
}

int Connection::close_sock(int sd) {
    if (close(sd) == -1)
        return 1;
    else
        return 0;
}

int Connection::listen_for(string ip, short porta) {
    struct sockaddr_in Server_addr;
    int listenSocket;
    int ls_result;
    /* Creazione della Socket che si porrÃ| n ascolto di richieste del Client*/
    listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listenSocket < 0) {
        cout << "Server: errore nella creazione della socket." << endl;
        return -1;
    }
    /* Effettua la bind sull.indirizzo e porta ora specificati */
    Server_addr.sin_family = AF_INET;
    Server_addr.sin_addr.s_addr = inet_addr(ip.c_str());
    Server_addr.sin_port = htons(porta);
    if (bind(listenSocket, (struct sockaddr*) &Server_addr, sizeof (Server_addr)) < 0) {
        cout << "Server: errore durante la bind." << endl;
        return -1;
    }
    /* La socket si pone in "ascolto" tramite la listen() */
    ls_result = listen(listenSocket, 20);
    if (ls_result < 0) {
        cout << "Errore durante la listen." << endl;
        return -1;
    }
    return listenSocket;


}

int Connection::file_to_string(char *&memblock, string filename, int &len) {
    ifstream::pos_type size;
    ifstream file(filename.c_str(), ios::in | ios::binary | ios::ate);
    if (file.is_open()) {
        size = file.tellg();
        memblock = new char [size];
        file.seekg(0, ios::beg);
        file.read(memblock, size);
        file.close();
        len = (int) size;
    } else {
        cout << "Unable to open file";
        return -1;
    }
    return 0;
}

int Connection::string_to_file(char *buffer, string filename, int len) {
    ofstream myfile(filename.c_str(), ios::out | ios::binary | ios::ate);
    if (myfile.is_open()) {
        myfile.write(buffer, len);
        myfile.close();
    } else {
        cout << "Unable to open file";
        return -1;
    }
    return 0;
}

int Connection::random_string(string &random) {
    unsigned int numero;
    numero = rand(); //rand() isn't not stable so the 1st assign may cause an error
    ostringstream out;
    out << numero;
    random.append(out.str());
    return 0;
}

int Connection::remove_file(string name) {
    if (remove(name.c_str()) != 0) {
        cout << "Errore nella cancellazione del file" << endl;
        return -1;
    }
    return 0;
}

string Connection::char_to_string(const char *elem){
    string convert(elem);
    
    return convert;
    
}
unsigned short Connection::char_to_uns_short(const char *elem){
    unsigned short numero;
    string numero_stringa(elem);
    istringstream buffer(numero_stringa);
    buffer >> numero;
    return numero;
    
    
}